package com.ares.comparer.utils;

import com.github.romankh3.image.comparison.exception.ImageComparisonException;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class ImageCompare {

    private int threshold = 5;

    private Color markDiffColor = new Color(255, 0, 0, 255);
    private Color maskColor = new Color(255, 255, 255, 150);

    private String expectedBufferedImagePath;
    private BufferedImage expectedBufferedImage;
    private int[][] expectedBufferedImageRGB;

    private String actualBufferedImagePath;
    private BufferedImage actualBufferedImage;
    private int[][] actualBufferedImageRGB;
    private ArrayList<HashMap<String, Integer>> diffList = new ArrayList<>();

    private String savePath;

    private Boolean isIgnoreImageSizeNuance = true;

    private Integer imageSizeNuance = 20;

    public ImageCompare(String expectedImagePath, String actualImagePath, Integer threshold, String savePath) {
        BufferedImage expectedBufferedImage = getBfImageFromPath(expectedImagePath);
        BufferedImage actualBufferedImage = getBfImageFromPath(actualImagePath);
        this.expectedBufferedImagePath = expectedImagePath;
        this.expectedBufferedImage = expectedBufferedImage;
        this.expectedBufferedImageRGB = getBufferedImageGRB(expectedBufferedImage);
        this.actualBufferedImagePath = actualImagePath;
        this.actualBufferedImage = actualBufferedImage;
        this.actualBufferedImageRGB = getBufferedImageGRB(actualBufferedImage);
        this.threshold = threshold;
        this.savePath = savePath;
    }

    /**
     * @param imagePath
     * @return
     * @author: ares
     * @date: 2023/3/3 13:48
     * @description: 根据路径获取bufferImage
     */
    public static BufferedImage getBfImageFromPath(String imagePath) {
        BufferedImage bfImage = null;
        try {
            bfImage = ImageIO.read(new File(imagePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bfImage;
    }

    /**
     * @param {BufferedImage}bufferedImage
     * @return
     * @author: ares
     * @date: 2023/3/3 11:18
     * @description: 获取bufferedImage的RGB数组
     */
    public static int[][] getBufferedImageGRB(BufferedImage bufferedImage) {
        int width = bufferedImage.getWidth();
        int height = bufferedImage.getHeight();
        int[][] result = new int[height][width];
        for (int h = 0; h < height; h++) {
            for (int w = 0; w < width; w++) {
                //使用getRGB(w, h)获取该点的颜色值是ARGB，而在实际应用中使用的是RGB，所以需要将ARGB转化成RGB，即bufImg.getRGB(w, h) & 0xFFFFFF。
                result[h][w] = bufferedImage.getRGB(w, h) & 0xFFFFFF;
            }
        }
        return result;
    }

    /**
     * @param image
     * @return
     * @author: ares
     * @date: 2023/3/3 14:37
     * @description: 复制图片
     */
    public static BufferedImage deepCopy(BufferedImage image) {
        ColorModel cm = image.getColorModel();
        boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
        WritableRaster raster = image.copyData(image.getRaster().createCompatibleWritableRaster());
        return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
    }

    /**
     * @author: ares
     * @date: 2023/3/3 19:29
     * @description: 获取文件名
     * @param url
     * @return
     */
    public static String getFileName(String url) {
        return Paths.get(url).getFileName().toString();
    }

    /**
     * @author: ares
     * @date: 2023/3/3 19:29
     * @description: 获取文件名和类型
     * @param url
     * @return
     */
    public static Map<String, String> getFileNameMap(String url) {
        Path path = Paths.get(url);
        HashMap<String, String> map = new HashMap<>();
        String[] fn = path.getFileName().toString().split("\\.");
        map.put("name", fn[0]);
        map.put("type", fn[1]);
        return map;
    }

    /**
     * @author: ares
     * @date: 2023/3/3 19:28
     * @description: 比较图片
     */
    public String compare() throws Exception {
        int widthAbs = Math.abs(this.expectedBufferedImage.getWidth() - this.actualBufferedImage.getWidth());
        int heightAbs = Math.abs(this.expectedBufferedImage.getHeight() - this.actualBufferedImage.getHeight());
        if(!this.isIgnoreImageSizeNuance) {
            throw new Exception("图片大小不一致");
        }
        if(this.isIgnoreImageSizeNuance && (widthAbs > this.imageSizeNuance || heightAbs > this.imageSizeNuance)){
            throw new Exception("图片大小差异太大");
        }
        getDiffList();
        BufferedImage image = masking(deepCopy(this.expectedBufferedImage));
        return saveImage(markDiff(image));
    }

    /**
     * @author: ares
     * @date: 2023/3/3 14:34
     * @description: 获取有差异的像素点
     */
    private ArrayList<HashMap<String, Integer>> getDiffList() {
        int expectedBufferedImageHeight = this.expectedBufferedImage.getHeight();
        int expectedBufferedImageWidth = this.expectedBufferedImage.getWidth();
        ArrayList<HashMap<String, Integer>> diffList = new ArrayList<>();

        for (int h = 0; h < expectedBufferedImageHeight; h++) {
            for (int w = 0; w < expectedBufferedImageWidth; w++) {
                int expectedRGB = this.expectedBufferedImageRGB[h][w];
                int actualRGB = this.actualBufferedImageRGB[h][w];

                int expectedR = (expectedRGB >> 16) & 0xFF;
                int expectedG = (expectedRGB >> 8) & 0xFF;
                int expectedB = (expectedRGB >> 0) & 0xFF;

                int actualR = (actualRGB >> 16) & 0xFF;
                int actualG = (actualRGB >> 8) & 0xFF;
                int actualB = (actualRGB >> 0) & 0xFF;

                int diffRAbs = Math.abs(expectedR - actualR);
                int diffGAbs = Math.abs(expectedG - actualG);
                int diffBAbs = Math.abs(expectedB - actualB);

                double averageR = (expectedR + actualR) / 2;
                double coefficientR = 2 + averageR / 256;
                double coefficientG = 4;
                double coefficientB = 2 + (255 - averageR) / 256;

                int powDiffRAbs = (int) Math.pow(diffRAbs, 2);
                int powDiffGAbs = (int) Math.pow(diffGAbs, 2);
                int powDiffBAbs = (int) Math.pow(diffBAbs, 2);

//                double algorithm = Math.sqrt(powDiffRAbs + powDiffGAbs + powDiffBAbs);
                double algorithm = Math.sqrt(coefficientR*powDiffRAbs + coefficientG*powDiffGAbs + coefficientB*powDiffBAbs);
                if (algorithm >= this.threshold) {
                    HashMap<String, Integer> hashMap = new HashMap();
                    hashMap.put("y", h);
                    hashMap.put("x", w);
                    diffList.add(hashMap);
                }
            }
        }
        this.diffList = diffList;
        return diffList;
    }

    /**
     * @author: ares
     * @date: 2023/3/3 19:27
     * @description: 添加蒙版
     * @param image
     * @return
     */
    private BufferedImage masking(BufferedImage image) {
        Graphics2D graphics = image.createGraphics();
        graphics.setColor(this.maskColor);
        graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
        return image;
    }

    /**
     * @author: ares
     * @date: 2023/3/3 19:24
     * @description:
     * @param image
     * @return
     */
    private BufferedImage markDiff(BufferedImage image) {
        Graphics2D graphics = image.createGraphics();
        graphics.setColor(this.markDiffColor);
        if(this.diffList.size()>0){
            this.diffList.forEach((HashMap<String, Integer> map) -> {
                Integer x = map.get("x") - 2;
                Integer y = map.get("y") - 2;
                graphics.fillRect(x, y, 4, 4);
            });
        }
        return image;
    }

    /**
     * @author: ares
     * @date: 2023/3/3 19:24
     * @description: 保存图片
     * @param image
     * @throws ImageComparisonException
     */
    private String saveImage(BufferedImage image) throws ImageComparisonException {
        String fileName = getFileNameMap(this.expectedBufferedImagePath).get("name") + "_" + getFileNameMap(this.actualBufferedImagePath).get("name") + "_diff.png";
        String fullPath = Paths.get(this.savePath, fileName).toString();
        File pathFile = new File(fullPath);
        File dir = pathFile.getParentFile();
        boolean dirExists = dir == null || dir.isDirectory() || dir.mkdirs();
        if (!dirExists) {
            throw new ImageComparisonException("Unable to create directory " + dir);
        }
        try {
            boolean write = ImageIO.write(image, "png", pathFile);
            if(write) return fullPath;
        } catch (IOException e) {;
            throw new ImageComparisonException(
                    String.format("Cannot save image to path=%s", pathFile.getAbsolutePath()), e);
        }
        return "";
    }

    public int getThreshold() {
        return threshold;
    }

    public void setThreshold(int threshold) {
        this.threshold = threshold;
    }

    public Color getMarkDiffColor() {
        return markDiffColor;
    }

    public void setMarkDiffColor(Color markDiffColor) {
        this.markDiffColor = markDiffColor;
    }

    public Color getMaskColor() {
        return maskColor;
    }

    public void setMaskColor(Color maskColor) {
        this.maskColor = maskColor;
    }

    public String getSavePath() {
        return savePath;
    }

    public void setSavePath(String savePath) {
        this.savePath = savePath;
    }

    public Boolean getIgnoreImageSizeNuance() {
        return isIgnoreImageSizeNuance;
    }

    public void setIgnoreImageSizeNuance(Boolean ignoreImageSizeNuance) {
        isIgnoreImageSizeNuance = ignoreImageSizeNuance;
    }

    public Integer getImageSizeNuance() {
        return imageSizeNuance;
    }

    public void setImageSizeNuance(Integer imageSizeNuance) {
        this.imageSizeNuance = imageSizeNuance;
    }
}
